/* 2017/05/07 */
package com.practice.reflection;

import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.List;

public class GenericTypePractice {

	public static void main(String[] args) {

		//getGenericType();
		
		//getTypeArgument();
		
		try {
			getTypeParameter();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
	
	protected static void getGenericType() {
		Type typeParameter = new GenericWrapper<List<String>>() {
		}.getType();
		System.out.println(typeParameter.getTypeName());
	}

	protected static void getTypeArgument() {
		
		Type infoType = new GenericWrapper<Info<?>>(){}.getType();
		System.out.println("Type: " + infoType.getTypeName());

		if (infoType instanceof ParameterizedType) {
			ParameterizedType parameterizedType = (ParameterizedType) infoType;
			System.out.println("Parameterized type: " + parameterizedType.getTypeName());
			
			Type[] typeArguments = parameterizedType.getActualTypeArguments();
			for (Type typeArgument : typeArguments) {
				System.out.println("Type argument: " + typeArgument.getTypeName());
				
				if (typeArgument instanceof Class) {
					Class<?> clazz = (Class<?>) typeArgument;
					System.out.println("Type is class: " + clazz.getName());
				}
				
				if (typeArgument instanceof WildcardType) {
					WildcardType wildcardType = (WildcardType) typeArgument;
					System.out.println("Type is wildcard type: " + wildcardType.getTypeName());
				}
			}
		}
	}

	protected static void getTypeParameter() throws Exception {

		Type genericType = new GenericWrapper<Info<String>>(){}.getType();
		System.out.println("Type: " + genericType.getTypeName());
		
		if (genericType instanceof ParameterizedType) {
			ParameterizedType parameterizedType = (ParameterizedType) genericType;
			System.out.println("Parameterized type: " + parameterizedType.getTypeName());
		}

		Class<?> genericClazz = Class.forName(((ParameterizedType) genericType).getRawType().getTypeName());
		System.out.println("Class: " + genericClazz.getName());
		TypeVariable<?>[] typeParameters = genericClazz.getTypeParameters();
		for (TypeVariable<?> typeParameter : typeParameters) {
			System.out.println("Type parameter: " + typeParameter.getName());			
			printTypeVariable(typeParameter);
		}
		
		Field genericField = genericClazz.getDeclaredField("value");
		Type genericFieldType = genericField.getGenericType();
		System.out.println("Type of generic field: " + genericFieldType.getTypeName());

		if (genericFieldType instanceof TypeVariable) {
			TypeVariable<?> typeVariable = (TypeVariable<?>) genericFieldType;
			System.out.println("Field type is type variable: " + typeVariable.getName());
			printTypeVariable(typeVariable);
		}
		
	}
	
	protected static void printTypeVariable(TypeVariable<?> typeVariable) {
		AnnotatedType[] annotatedBounds = typeVariable.getAnnotatedBounds();
		for (AnnotatedType annotatedType : annotatedBounds) {
			System.out.println("Annotated type: " + annotatedType.getType().getTypeName());
		}
		GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
		System.out.println("Generic declaration: " + genericDeclaration.getClass().getName());
	}

}

class GenericWrapper<E> {
	Type getType() {
		Type type = getClass().getGenericSuperclass();
		ParameterizedType paramType = (ParameterizedType) type;
		return (Type) paramType.getActualTypeArguments()[0];
	}
}

class Info<T> {

	private T value;

	public T getValue() {
		return value;
	}

	public void setValue(T value) {
		this.value = value;
	}

}

class StringInfo extends Info<String> {

}
